package org.xx.armory.junit;

import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.xx.armory.commons.Action;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Objects;

import static org.xx.armory.commons.Validators.notNull;

/**
 * 用于JUNIT断言的工具类。
 * <p>此工具类提供了一系列{@link Matcher}，用来执行特定的检测。</p>
 *
 * @author Haart
 */
public final class AssertHelper {

    /**
     * 禁止构造{@link AssertHelper}类型的实例。
     */
    private AssertHelper() {
        throw new UnsupportedOperationException();
    }

    /**
     * 比较两个对象的所有字段是否都相等。
     *
     * @param clazz
     *         两个对象的类型。
     * @param expected
     *         期望的对象。
     * @param actual
     *         实际对象。
     * @throws NullPointerException
     *         如果参数{@code clazz}或者{@code expected}或者{@code actual}是{@code null}。
     * @throws ReflectiveOperationException
     *         如果待比较的两个对象不是同一种类型，或者两个对象中至少有一个不是{@code clazz}类型，或者访问对象的字段时出错。
     * @see Objects#deepEquals(Object, Object)
     */
    private static boolean allFieldsEquals(
            Class<?> clazz,
            Object expected,
            Object actual
    )
            throws ReflectiveOperationException {
        notNull(clazz, "clazz");
        notNull(expected, "expected");
        notNull(actual, "actual");

        for (final Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            if (!Objects.deepEquals(field.get(expected), field.get(actual))) {
                return false;
            }
        }

        return true;
    }

    /**
     * 返回一个用于执行逐字段匹配的Matcher。此Mather用于检测指定的对象是否和期望的对象完全相等，即类型相同且每个字段都相等。
     *
     * @param <T>
     *         期望的对象的类型。
     * @param expected
     *         期望得到的对象。
     * @return 用于执行测试的Matcher。
     * @throws IllegalArgumentException
     *         如果参数{@code expected}是{@code null}。
     */
    public static <T> Matcher<T> deepEquals(
            T expected
    ) {
        notNull(expected, "expected");

        return new BaseMatcher<T>() {
            @Override
            public boolean matches(
                    Object o
            ) {
                try {
                    return o != null && (expected == o || Objects.deepEquals(expected, o) ||
                            expected.getClass() == o.getClass() && allFieldsEquals(expected.getClass(), expected, o));
                } catch (ReflectiveOperationException ex) {
                    throw new AssertionError("cannot compare objects", ex);
                }
            }

            @Override
            public void describeTo(Description description) {
                description.appendText("not equals to " + expected);
            }
        };
    }

    /**
     * 返回一个用于执行测试的Matcher。此Mather用于检测指定的Action是否抛出了类型为{@code clazz}或者其子类的异常。
     *
     * @param expectedException
     *         期望抛出的异常的类型。
     * @return 用于执行测试的Matcher。
     * @throws IllegalArgumentException
     *         如果参数{@code clazz}是{@code null}。
     */
    public static Matcher<Action<?>> shouldFailWith(Class<? extends Exception> expectedException) {
        notNull(expectedException, "expectedException");

        return new BaseMatcher<Action<?>>() {
            @Override
            public boolean matches(Object o) {
                @SuppressWarnings("unchecked") Action<?> action = (Action<?>) o;
                try {
                    action.call();
                    return false;
                } catch (Exception ex) {
                    return expectedException.isAssignableFrom(ex.getClass());
                }
            }

            @Override
            public void describeTo(Description description) {
                description.appendText("a action obj throws " + expectedException.getName());
            }
        };
    }

    /**
     * 返回一个用于测试的Matcher，此Matcher用于检测指定的对象是否非空。
     * <p>只能用于{@link CharSequence}或者{@link Array}或者{@link Collection}</p>
     *
     * @return 用于执行测试的Matcher。
     */
    public static Matcher<Object> notEmpty() {
        return new BaseMatcher<Object>() {
            @Override
            public boolean matches(Object o) {
                if (o == null) {
                    throw new AssertionError("actual object cannot be null");
                } else if (o instanceof CharSequence) {
                    return ((CharSequence) o).length() > 0;
                } else if (o.getClass().isArray()) {
                    return Array.getLength(o) > 0;
                } else if (o instanceof Collection<?>) {
                    return ((Collection<?>) o).size() > 0;
                } else {
                    throw new AssertionError("actual object should be char sequence or array or collection");
                }
            }

            @Override
            public void describeTo(Description description) {
                description.appendText("a char sequence or array or collection is not empty");
            }
        };
    }
}
